﻿// --------------------------------------------------------------------------------------------------------------------
// <summary>
//   Defines the RegistryAlarmsConfig type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Wolfpack.Contrib.Checks.RegistryAlarms
{
    using System;
    using System.IO;

    using Microsoft.Win32;

    using Wolfpack.Core;
    using Wolfpack.Core.Interfaces;
    using Wolfpack.Core.Interfaces.Entities;

    /// <summary>
    /// Defines the settings to pull from the config file. 
    /// </summary>
    public class RegistryAlarmConfig : PluginConfigBase
    {
        /// <summary>
        /// Gets or sets SubKey. In HKLM\Software\Microsoft\FilePaths TempDir='test' where Software\Microsoft\Filepaths is the SubKey.
        /// </summary>
        public String SubKey { get; set; }

        /// <summary>
        /// Gets or sets Keyname.  In HKLM\Software\Microsoft\FilePaths TempDir='test' where TempDir is the KeyName.
        /// </summary>
        public String Keyname { get; set; }

        /// <summary>
        /// Gets or sets the name of the registry Hive. Examples: ClassesRoot|CurrentConfig|CurrentUser|PerformanceData|Users|LocalMachine
        /// Defaults to LocalMachine.
        /// </summary>
        public String Hive { get; set; }

        /// <summary>
        /// Gets or sets the name of the registry alarm type. Valid values are GreaterThan|LesserThan|EqualTo|OnChange
        /// 
        /// </summary>
        public RegistryAlarmType RegistryAlarmType { get; set; }

        /// <summary>
        /// Gets or sets the Operand. Registry Operand on Left, Configuration Operand on Right
        /// </summary>
        public String Operand { get; set; }
    }

    /// <summary>
    /// The Plugin.
    /// </summary>
    public class RegistryAlarm : IHealthCheckPlugin
    {
        /// <summary>
        /// The config file. (registry.castle.config)
        /// </summary>
        private readonly RegistryAlarmConfig myConfig;

        /// <summary>
        /// Initializes a new instance of the <see cref="RegistryAlarm"/> class.
        /// </summary>
        /// <param name="config">
        /// The config file.
        /// </param>
        public RegistryAlarm(RegistryAlarmConfig config)
        {
            this.myConfig = config;
        }

        /// <summary>
        /// Gets or sets Status.
        /// </summary>
        public Status Status { get; set; }

        /// <summary>
        /// Gets Identity.
        /// </summary>
        public PluginDescriptor Identity
        {
            get
            {
                return new PluginDescriptor
                    {
                        Description = "RegistryAlarms",
                        Name = this.myConfig.FriendlyId,

                        // The TypeId is important - it needs to be different for
                        // each health check as this allows us to positively identify
                        // every health check. Use the VS Tools/Create GUID tool to
                        // generate a new one.
                        TypeId = new Guid("31F7FB00-1E32-4989-8902-785A868228AC")
                    };
            }
        }

        /// <summary>
        /// Performs a one-time initialization.
        /// </summary>
        public void Initialise()
        {
        }

        /// <summary>
        /// Checks the current registry value against the last known value.  If no known value exists or
        /// if the value has changed, send a message.
        /// </summary>
        public void Execute()
        {
            if (String.IsNullOrWhiteSpace(this.myConfig.RegistryAlarmType.ToString())
               || String.IsNullOrWhiteSpace(this.myConfig.SubKey)
               || String.IsNullOrWhiteSpace(this.myConfig.Keyname))
            {
                Logger.Debug("Error in RegistryAlarm config.", this.myConfig.FriendlyId);
                return;
            }

            Logger.Debug("Health Check: {0} is checking value {1} for {2} in {3}\\{4}",
            this.myConfig.FriendlyId,
            this.myConfig.Operand,
            this.myConfig.RegistryAlarmType,
            this.myConfig.SubKey,
            this.myConfig.Keyname);

            RegistryKey registryKey;
            String registryString;

            switch (this.myConfig.Hive)
            {
                case "ClassesRoot":
                    registryKey = Registry.ClassesRoot;
                    break;
                case "CurrentConfig":
                    registryKey = Registry.CurrentConfig;
                    break;
                case "CurrentUser":
                    registryKey = Registry.CurrentUser;
                    break;
                case "PerformanceData":
                    registryKey = Registry.PerformanceData;
                    break;
                case "Users":
                    registryKey = Registry.Users;
                    break;
                default:
                    registryKey = Registry.LocalMachine;
                    break;
            }

            try
            {
                if(String.IsNullOrWhiteSpace(registryString = registryKey.GetValue64(this.myConfig.SubKey, this.myConfig.Keyname).ToString()))
                {
                    Logger.Debug("Registry value not found {0} {1}.", this.myConfig.SubKey, this.myConfig.Keyname);
                    return;
                }
            }
            catch (Exception ex)
            {
                Logger.Debug("Registry key not found {0} {1}. Exception: {2}", this.myConfig.SubKey, this.myConfig.Keyname, ex.Message);
                return;
            }

            switch (this.myConfig.RegistryAlarmType.ToString())
            {
                case "OnChange":
                    this.OnChange(registryString);
                    break;
                default:
                    this.Compare(registryString);
                    break;
            }
        }

        /// <summary>
        /// Only reports a registry alarm if the value has changed.
        /// Check for null, empty or white space before calling this method.
        /// </summary>
        /// <param name="registryString">The string to compare.</param>
        public void OnChange(string registryString)
        {
            try
            {
                FileInfo filename =
                new FileInfo(
                    Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Working", this.myConfig.FriendlyId + ".txt"));

                String lastValue;

                if (filename.Exists)
                {
                    using (StreamReader sr = filename.OpenText())
                    {
                        lastValue = sr.ReadLine();
                        sr.Close();
                        sr.Dispose();
                    }

                    if (lastValue.Trim().Equals(registryString.Trim()))
                    {
                        return;
                    }
                }
                else
                {
                    DirectoryInfo di = new DirectoryInfo(filename.DirectoryName);
                    if (!di.Exists)
                    {
                        try
                        {
                            di.Create();
                        }
                        catch (Exception ex)
                        {
                            Logger.Error("Unable to create directory for registry read. Exception: {0}, ", ex.Message);
                            return;
                        }
                    }
                }

                using (StreamWriter sw = new StreamWriter(filename.ToString(), false))
                {
                    sw.WriteLine(registryString);
                    sw.Flush();
                    sw.Close();
                    sw.Dispose();
                }
            }
            catch (Exception ex)
            {
                Logger.Debug("Error in RegistryAlarm OnChange HealthCheck:{0}. Exception: {1}.",
                    this.myConfig.FriendlyId,
                    ex.Message);
                return;
            }

            this.Publish(registryString);
        }

        /// <summary>
        /// Compares the registry entry with a value in the config file.
        /// Check for null, empty or white space before calling this method.
        /// </summary>
        /// <param name="registryString">The string to compare.</param>
        public void Compare(String registryString)
        {
            try
            {
                Decimal registryOperand = Convert.ToDecimal(registryString);
                Decimal configOperand = Convert.ToDecimal(this.myConfig.Operand);

                int result = Decimal.Compare(registryOperand, configOperand);

                switch (this.myConfig.RegistryAlarmType.ToString())
                {
                    case ("EqualTo"):
                        if (result == 0)
                        {
                            this.Publish(registryOperand.ToString());
                        }

                        break;

                    case ("GreaterThan"):
                        if (result > 0)
                        {
                            this.Publish(registryOperand.ToString());
                        }

                        break;

                    case ("LesserThan"):
                        if (result < 0)
                        {
                            this.Publish(registryOperand.ToString());
                        }

                        break;

                    default:
                        Logger.Info("Incorrect RegistryAlarmType in {0} config file.", this.myConfig.FriendlyId);
                        break;
                }
            }
            catch (Exception ex)
            {
                Logger.Debug("Error during compare.", ex.Message);
                return;
            }
        }

        /// <summary>
        /// Reports information back to the Wolfpack publisher.
        /// </summary>
        /// <param name="registryValue">The value to report back to the Wolfpack publisher.</param>
        public void Publish(String registryValue)
        {
            if (String.Compare(registryValue, "<value not found>") != 0)
            {
                Logger.Debug("Registry Alarm Condition met! Publishing check {0}.", this.myConfig.FriendlyId);
                Messenger.Publish(
                    new HealthCheckData
                    {
                        Identity = this.Identity,
                        Result = false,
                        Info = this.myConfig.SubKey + @"\" + this.myConfig.Keyname + "=" + registryValue,
                        Tags = registryValue.ToString()
                    });
            }
            else
            {
                Logger.Debug("<value not found> skipping publish.");
            }

        }
    }
}